#include <tinx/device.h>
#include <tinx/interrupt.h>
#include <tinx/io.h>
#include <tinx/memory.h>
#include <tinx/string.h>

#define CRT_ADDR_REG 0x3D4 // CRT(6845)索引寄存器
#define CRT_DATA_REG 0x3D5 // CRT(6845)数据寄存器

#define CRT_START_ADDR_H 0xC // 显示内存起始位置 - 高位
#define CRT_START_ADDR_L 0xD // 显示内存起始位置 - 低位
#define CRT_CURSOR_H 0xE     // 光标位置 - 高位
#define CRT_CURSOR_L 0xF     // 光标位置 - 低位

#define MEM_BASE 0xB8000              // 显卡内存起始位置
#define MEM_SIZE 0x4000               // 显卡内存大小
#define MEM_END (MEM_BASE + MEM_SIZE) // 显卡内存结束位置
#define WIDTH 80                      // 屏幕文本列数
#define HEIGHT 25                     // 屏幕文本行数
#define ROW_SIZE (WIDTH * 2)          // 每行字节数
#define SCR_SIZE (ROW_SIZE * HEIGHT)  // 屏幕字节数

#define NUL 0x00
#define ENQ 0x05
#define ESC 0x1B // ESC
#define BEL 0x07 // \a
#define BS 0x08  // \b
#define HT 0x09  // \t
#define LF 0x0A  // \n
#define VT 0x0B  // \v
#define FF 0x0C  // \f
#define CR 0x0D  // \r
#define DEL 0x7F

enum ST
{
    ST_NORMAL = 0,
    ST_BOLD = 1,
    ST_BLINK = 5,
    ST_REVERSE = 7,
};

#define STYLE 7
#define BLINK 0x80
#define BOLD 0x0F
#define UNDER 0X0F

enum COLOR
{
    BLACK = 0,
    BLUE = 1,
    GREEN = 2,
    CYAN = 3,
    RED = 4,
    MAGENTA = 5,
    YELLOW = 6,
    WHITE = 7,
};

typedef struct console_t
{
    union
    {
        u32 pos;
        u16 *ptr;
    };
    u16 eraser;
    u16 style;
    u32 mem_base;
    u32 mem_size;
    u32 mem_end;
    u32 screen;
} console_t;

static console_t console;

static void set_cursor(console_t *con)
{
    outb(CRT_ADDR_REG, CRT_CURSOR_H);
    outb(CRT_DATA_REG, (con->pos - con->mem_base) >> 9 & 0xff);
    outb(CRT_ADDR_REG, CRT_CURSOR_L);
    outb(CRT_DATA_REG, (con->pos - con->mem_base) >> 1 & 0xff);
}

static void set_screen(console_t *con)
{
    outb(CRT_ADDR_REG, CRT_START_ADDR_H);
    outb(CRT_DATA_REG, (con->screen - con->mem_base) >> 9 & 0xff);
    outb(CRT_ADDR_REG, CRT_START_ADDR_L);
    outb(CRT_DATA_REG, (con->screen - con->mem_base) >> 1 & 0xff);
}

static void scroll_up(console_t *con)
{
    if (con->screen + SCR_SIZE < con->mem_end)
    {
        con->screen += ROW_SIZE;
        set_screen(con);
    }
    else
    {
        memcpy((char *)con->mem_base, (char *)con->screen, SCR_SIZE);
        memset((char *)(con->mem_base + SCR_SIZE), 0, con->mem_size - SCR_SIZE);
        con->screen = con->mem_base;
        con->pos = con->screen + SCR_SIZE;
        scroll_up(con);
    }
}

static void cr(console_t *con)
{
    con->pos -= (con->pos - con->mem_base) % ROW_SIZE;
}

inline static void lf(console_t *con)
{
    con->pos += ROW_SIZE;
    if (con->pos >= con->screen + SCR_SIZE)
        scroll_up(con);
}

inline static void chr(console_t *con, char ch)
{
    *con->ptr++ = ch | con->style;
    if (con->pos >= con->screen + SCR_SIZE)
        scroll_up(con);
}

inline static void bs(console_t *con)
{
    *--con->ptr = con->eraser;
}

int vgacon_write(console_t *con, char *data, int count)
{
    bool intr = interrupt_disable();

    int nr = 0;
    while (nr < count)
    {
        char ch = data[nr++];
        switch (ch)
        {
        case LF:
            lf(con);
        case CR:
            cr(con);
            break;
        case BS:
            bs(con);
            break;
        default:
            chr(con, ch);
            break;
        }
    }

    set_cursor(con);
    set_interrupt_state(intr);

    return nr;
}

void vgacon_init()
{
    console_t *con = &console;
    con->mem_base = MEM_BASE;
    con->mem_size = MEM_SIZE;
    con->mem_end = MEM_BASE + MEM_SIZE;
    con->screen = con->mem_base;
    con->ptr = (u16 *)con->mem_base;
    con->style = (7 << 8);
    con->eraser = (7 << 8) | 0x20;

    device_create(
        DEV_TYPE_CHAR, DEV_FLAG_CONSOLE, -1, "console",
        con, vgacon_write, NULL, NULL);
}